#ifndef TABLE_HH
#define TABLE_HH

#include "stg_mgr.hh"
#include "buf_mgr.hh"
#include "block.hh"
#include "bplus_tree.hh"

#undef REMOVE
/*****	TABLE >>> contains information on Table File, containing Number of Pages/Records/Attributes *****/
struct tableinfo {
	char table_name[TABLE_NAME_LENGTH];			// table()
	int num_of_attribs;					// table()
	char attrib_names[MAX_NUM_ATTRIBS][ATTRIB_NAME_LENGTH];	// table()
	int num_of_records;					// table() insert_record() delete_record()
	int num_indexes;
	indexinfo index_info[MAX_INDEXES];
	
};

class table
{
	struct tableinfo	table_info;
     	class stm		*stm1;		// table()
	class buffer		*mbuf_mgr;
	list <bplustree *> 	ilist;
	//class bplustree 	*mindexes[MAX_INDEXES];	// table() insert_record()

public:
	class table	*next;		// used by the catalog to point to next table

	/*** Generic functions ***/
	table(tableinfo *tinfo1, buffer *buf1);
	int insert_record(int* arr);
	int  add_index(char *iname, char *attribname, int dups_flag);
	void commit();
	~table();

	int get_attrib_offset(char *attrib_name);
	int form_bitmap(int num, char *attriblist, int *attribmap);

	/*** To print information about the table ***/
	int	print_attriblist(int num, char *attriblist, clause *qry);
	int	print_table(int num, int *attribmap, clause *qry);
	int	print_index(char *iname);
	void	print_stats();
	int	print();


	/*** Get functions to get the variables for this table. ***/
	stm*		get_stm()		{ return(stm1); }
	tableinfo *	tbl_info()		{ return(&table_info); }
	bplustree*	find_index(char *iname);
	bplustree*	find_index(char *iname, int offset);
	int		insert_row_into_index(int *aRow, int blockno, bplustree *b1);

	char*		get_table_name()	{ return(table_info.table_name); }
	int 		no_of_attribs()		{ return(table_info.num_of_attribs); }
	int 		no_of_records()		{ return(table_info.num_of_records); }

	/*** No. of attribs * size of attrib = size of a record ***/
	int size_of_record()		{ return(table_info.num_of_attribs * sizeof(int)); }

	/*** Size of the block / Size of the record = No. of records per block ***/
	int records_per_block()		{ return(BLOCK_LENGTH / size_of_record()); }

	/*** Upper bound of (No.of Records / Records per block) = No. of Blocks ***/
	int no_of_blocks()		{ return( (int) ceil((double)table_info.num_of_records/(double)records_per_block())); }
	int block_idx_for_new_record()	{ 
	return( (int) ceil(((double)table_info.num_of_records+1)/(double)records_per_block()) -1); }

	/*** Num of Blocks * Size of Block = Size of File ***/
	int size_of_file()		{ return(no_of_blocks() * BLOCK_LENGTH); }

	/*** Number of Records - (Num Blocks -1 * Records per block) = Records in the last block ***/
	int records_in_last_block();
};

int table :: records_in_last_block()
{
	if(table_info.num_of_records == 0)
		return 0;
	else
		return(table_info.num_of_records - ((no_of_blocks() - 1) * records_per_block()));
}

table :: table(tableinfo *tinfo1, buffer *buf1)
{
	indexinfo *iinfo;
	int i=0;

	strcpy(table_info.table_name,tinfo1->table_name);
	table_info.num_of_attribs = tinfo1->num_of_attribs;
	table_info.num_of_records = tinfo1->num_of_records;
	table_info.num_indexes = tinfo1->num_indexes;

	DBGPRINTS(table_info.table_name);
	DBGPRINTN(table_info.num_of_attribs);
	DBGPRINTN(table_info.num_of_records);
	DBGPRINTN(table_info.num_indexes);

	for(i=0;i<table_info.num_of_attribs;i++)
	{
		DBGPRINTS(tinfo1->attrib_names[i]);
		strcpy(table_info.attrib_names[i], tinfo1->attrib_names[i]);
	}
	
	mbuf_mgr = buf1;
	for(i=0;i<table_info.num_indexes;i++)
	{
		iinfo = &table_info.index_info[i];
		strcpy(iinfo->iname, tinfo1->index_info[i].iname);
		iinfo->offset = tinfo1->index_info[i].offset;
		iinfo->dups = tinfo1->index_info[i].dups;
		iinfo->block_count = tinfo1->index_info[i].block_count;
		iinfo->root_block = tinfo1->index_info[i].root_block;

		DBGPRINTS(iinfo->iname);
		DBGPRINTN(iinfo->offset);

		/*** Create an instance of bplus tree for every entry ***/
		bplustree *b1;
		b1 = new bplustree(mbuf_mgr, iinfo);
		ilist.push_front(b1);
	}

	stm1 = new stm(table_info.table_name);
	next = NULL;

}	

int table :: insert_record(int* arr)
{
	int	*aRow;
	block	*temp;
	bplustree *b1;

	if(records_per_block() == records_in_last_block() )
	{
		DBGPRINTN(block_idx_for_new_record());

		if(mbuf_mgr->get_new_block(table_info.table_name, block_idx_for_new_record(), &temp))
		{
			return(INTERNAL_ERROR);
		}
		temp->initialize(table_info.table_name, table_info.num_of_attribs, block_idx_for_new_record(), 0, stm1);
		if(temp->insert_record(arr))
		{
			return(INTERNAL_ERROR);
		}
		
	}
	else
	{
		DBGPRINTN(block_idx_for_new_record());

		if(mbuf_mgr->get_block_num(table_info.table_name, block_idx_for_new_record(), &temp) )
		{
			return(INTERNAL_ERROR);
		}
		temp->initialize(table_info.table_name, table_info.num_of_attribs, block_idx_for_new_record(), records_in_last_block(), stm1);
		if(temp->insert_record(arr))
		{
			return(INTERNAL_ERROR);
		}
	}
	table_info.num_of_records++;

	DBGMEMPRINT(arr, no_of_attribs());

	for(list<bplustree *>::iterator i= ilist.begin(); i != ilist.end();)
	{
		b1 = *i;
		if(insert_row_into_index(arr, temp->get_block_no(), b1))
		{
			i = ilist.erase(i);
			remove(b1->iname);
#ifdef REMOVE
			if(remove(b1->iname) != 0 )
				FATAL("Error deleting file %s",b1->iname);
			else
				FATAL("Index %s successfully deleted",b1->iname );
#endif

		}
		else
		{
			i++;
		}
	}



#ifdef DEBUG
	HEAD("##### Active Pool #####\n");
	mbuf_mgr->print_active_pool();
#endif
	return(0);
}

bplustree* table :: find_index(char *iname)
{
	return(find_index(iname, -1));
}

bplustree* table :: find_index(char *iname, int offset)
{

	bplustree *temp;

	DBGPRINTS(iname);
	DBGPRINTN(offset);
	DBGPRINTS(table_info.attrib_names[offset]);

	for(list<bplustree *>::iterator i= ilist.begin(); i != ilist.end(); i++)
	{
		temp = *i;
		if(offset == temp->attrib_id)
		{
			DBGPRINTS(temp->iname);
			DBGPRINTN(temp->attrib_id);
			DBGPRINTN(temp->dups);
			DBGPRINTF("Index found with matching offset\n");
			return(temp);
		}
		else if(!strcmp(iname, temp->iname))
		{
			DBGPRINTS(temp->iname);
			DBGPRINTN(temp->attrib_id);
			DBGPRINTN(temp->dups);
			DBGPRINTF("Index found with matching name\n");
			return(temp);
			
		}
	}

	return(NULL);
}
int table :: print_index(char *iname)
{
	int i=0, j=0;
	block *temp;
	bplustree *b1;
	
	if(NULL == (b1=find_index(iname)))
	{
		FATAL("Index %s not present in %s\n", b1->iname, table_info.table_name);
		return(DATA_NOT_FOUND);
	}
	else
	{

		SHEAD("Index Name: %s\n", b1->iname);
		SHEAD("Table Name: %s\n", table_info.table_name);
		SHEAD("Attrib Name: %s\n", table_info.attrib_names[b1->attrib_id]);
		SHEAD("Duplicates: %s\n", (b1->dups)? "Supported" : "Not Supported");

		// Call to print the index buckets etc ...
		if(iDebugON)	b1->printIndex();
		return(0);

	}
	return(0);

}


int table :: add_index(char *iname, char *attribname, int dups_flag)
{
	int offset=0;
	int i=0, j=0;
	int aRow[MAX_NUM_ATTRIBS];

	block *temp;
	indexinfo *iinfo;
	bplustree *b1;

	table_info.num_indexes = ilist.size();

	if(table_info.num_indexes == MAX_INDEXES)
	{
		FATAL("Indexes reached the MAX limit of %d\n", table_info.num_indexes);
		return(DATA_NOT_FOUND);
	}

	offset = get_attrib_offset(attribname);
	if(offset == table_info.num_of_attribs)
	{
		FATAL("Attribute %s not found in  %s\n", attribname, table_info.table_name);
		return(DATA_NOT_FOUND);
	}

	if(NULL == (b1=find_index(iname, offset)))
	{
		iinfo = &table_info.index_info[table_info.num_indexes];
		strcpy(iinfo->iname, iname);	
		iinfo->offset = offset;
		iinfo->dups   = dups_flag;
		iinfo->block_count = 0;
		iinfo->root_block = 0;

		// Call to create new index entry
		b1 = new bplustree(mbuf_mgr, iinfo);
		ilist.push_front(b1);

		// Call for inserting all the records already present into the index
		for(i=0; i<no_of_blocks()-1; i++)
		{
			mbuf_mgr->get_block_num(table_info.table_name, i, &temp);
			temp->initialize(table_info.table_name, table_info.num_of_attribs, i, records_per_block(), stm1);
			for(j=0; j<records_per_block(); j++)
			{
				memcpy(aRow, &temp->buffer[j*no_of_attribs()], size_of_record());
				if(insert_row_into_index(aRow, temp->get_block_no(), b1))
				{
					ilist.remove(b1);
					remove(b1->iname);

#ifdef REMOVE
					if(remove(b1->iname) != 0 )
						FATAL("Error deleting file %s",b1->iname);
					else
						FATAL("Index %s successfully deleted",b1->iname );
#endif

					return 1;
				}
			}
		}

		/*** Get last block of the table, initialize it with correct number of records!!! ***/
		mbuf_mgr->get_block_num(table_info.table_name, i, &temp);
		temp->initialize(table_info.table_name, table_info.num_of_attribs, i, records_in_last_block(), stm1);
		for(j=0; j<records_in_last_block(); j++)
		{
			memcpy(aRow, &temp->buffer[j*no_of_attribs()], size_of_record());
			if(insert_row_into_index(aRow, temp->get_block_no(), b1))
			{
				ilist.remove(b1);
				remove(b1->iname);

#ifdef REMOVE			
				if(remove(b1->iname) != 0 )
					FATAL("Error deleting file %s",b1->iname);
				else
					FATAL("Index %s successfully deleted",b1->iname );
#endif
				return 1;
			}
		}

		//		b1->printIndex();

		table_info.num_indexes++;
		DBGPRINTF("Index %s created on <%s> with Dups=%d\n",iname, table_info.attrib_names[offset], dups_flag);

	}
	else
	{
		FATAL("Index %s on <%s> already present\n", b1->iname, table_info.attrib_names[b1->attrib_id]);
		return(1);

	}
	return(0);
}

int table :: insert_row_into_index(int *aRow, int blockno, bplustree *b1)
{
	int i=0;
	vector<int> table_blocks;

	if(b1==NULL)
		return 0;

	if(!b1->dups)
	{
		table_blocks = b1->getTableBloks(aRow[b1->attrib_id], iDebugON);
		FATAL("found %d blocks containing %d\n",table_blocks.size(), aRow[b1->attrib_id]); //MBM

		if(table_blocks.size() > 0)
		{
			FATAL("Index %s on <%s> cannot be created, due to duplicates\n", b1->iname, table_info.attrib_names[b1->attrib_id]);
			return 1;
		}
	}
	b1->insertInMemory(aRow, blockno);

	return 0;

}

void table :: print_stats()
{
	int i=0;

	SHEAD("***** Table: %s *****", table_info.table_name);
	PRINT(" (");
	for (i=0; i<no_of_attribs(); i++)
		PRINT("-%s-", table_info.attrib_names[i]);
	PRINT(")\n");
	LINE("Number of attribs:    %8d\n", no_of_attribs());
	LINE("Number of records:    %8d\n", no_of_records());
	LINE("Size of each record:  %8d\n", size_of_record());
	LINE("Number of Pages req.: %8d\n", no_of_blocks());
	LINE("Size of the File:     %8d\n", size_of_file());
	LINE("Num of Indexes:       %8d\n", ilist.size());
	
	for (i=0; i<ilist.size(); i++)
		print_index(table_info.index_info[i].iname);
}

int table :: print_attriblist(int numattribs, char *attriblist, clause *qry)
{
	int attrib_map[MAX_NUM_ATTRIBS];
	int i=0, j=0;
	int found=0;


	if(!strcmp(&attriblist[0], "*"))
	{
		for(i=0; i<table_info.num_of_attribs; i++)
		{
			attrib_map[i]=i;
		}
		numattribs=table_info.num_of_attribs;
		
	}
	else if(form_bitmap(numattribs, attriblist, attrib_map) )
	{
		FATAL("Unable to Map the attributes to that in the list\n");
		return DATA_NOT_FOUND;
	}

	// Map the attributes in the qry
	if(qry!=NULL)
	{
		found = 0;
		for(j=0; j<table_info.num_of_attribs; j++)
		{
			if( !strcmp(qry->u.attrib, table_info.attrib_names[j]) )
			{
				qry->u.numattrib=j;
				found=1;
				break; 
			}
		}
		DBGPRINTN(qry->u.numattrib);

		if(!found)
		{
			FATAL("Unable to Map the attributes in the where clause\n");
			return(DATA_NOT_FOUND);
		}

	}


	print_table(numattribs, attrib_map, qry);

	return 0;
}

int table :: form_bitmap(int numattribs, char *attriblist, int *attribmap)
{
	int i=0;


	// i - for parsing the input attributes
	// j - for checking them with the ones in the table
	for(i=0;i<numattribs;i++)
	{
		attribmap[i] = get_attrib_offset( &attriblist[i*ATTRIB_NAME_LENGTH] ) ;
		if(attribmap[i] == table_info.num_of_attribs)
		{
			FATAL("Attribute %s not found in  %s\n", &attriblist[i*ATTRIB_NAME_LENGTH], table_info.table_name);
			return(DATA_NOT_FOUND);
		}
	}

	return (0);
	

}

int table :: get_attrib_offset(char *aname)
{
	int j=0;
	
	for(j=0; j<table_info.num_of_attribs; j++)
	{
		DBGPRINTS(aname);
		DBGPRINTS(table_info.attrib_names[j]);
		if( !strcmp(aname, table_info.attrib_names[j]) )
		{
			DBGPRINTF("Attribute %s found in %s\n", aname, table_info.table_name);
			
			break; //break the loop when it is found
		}
	}
	
	return j;
}

int table :: print_table(int num, int *attribmap, clause *qry)
{
	int i=0;
	int found=0;
	block *temp;
	bplustree *b1;


	HEAD("Table: %8s", table_info.table_name);
	HEAD("------\n");

	for (i=0; i<num; i++)
	{
		PRINT("%8s ", table_info.attrib_names[attribmap[i]]);
	}

	if(0 == no_of_records())
	{
		SHEAD("!!! No Records found !!!\n");
		return 0;
	}

	//assumes it is for equals
	if(qry != NULL)
	{
		for(list<bplustree *>::iterator t = ilist.begin(); t != ilist.end(); t++)
		{
			b1 = *t;
			if (b1->attrib_id == qry->u.numattrib) { found=1;break; }
		}

		if(found == 0)
		{ 
			// Continue in normal fashion
			PRINT("\nIndex not found on attribute %s\n", table_info.attrib_names[qry->u.numattrib]);
		}
		else
		{
			int index = i;
			vector<int> table_blocks;

			table_blocks = b1->getTableBloks(qry->val, iDebugON);
			DBGPRINTF("\nNo of Blocks is %d\n",table_blocks.size());
			for(i=0; i<table_blocks.size();i++)
			{
				DBGPRINTN(table_blocks[i]);
				mbuf_mgr->get_block_num(table_info.table_name,table_blocks[i],&temp);
				if(table_blocks[i] == (no_of_blocks()-1)) 
					temp->initialize(table_info.table_name,table_info.num_of_attribs,table_blocks[i],records_in_last_block(),stm1);
				else
					temp->initialize(table_info.table_name,table_info.num_of_attribs,table_blocks[i],records_per_block(),stm1);
					
				temp->print_records(num,attribmap,qry);
			}
			return 0;

		}
	}

	for(i=0; i<no_of_blocks()-1; i++)
	{
		mbuf_mgr->get_block_num(table_info.table_name, i, &temp);
		temp->initialize(table_info.table_name, table_info.num_of_attribs, i, records_per_block(), stm1);
		temp->print_records(num, attribmap, qry);
	}

	/*** Get last block of the table, initialize it with correct number of records!!! ***/
	mbuf_mgr->get_block_num(table_info.table_name, i, &temp);
	temp->initialize(table_info.table_name, table_info.num_of_attribs, i, records_in_last_block(), stm1);
	temp->print_records(num, attribmap, qry);

	return 0;
}

int table :: print()
{
	int attrib_map[MAX_NUM_ATTRIBS];
	int i=0, num = table_info.num_of_attribs;

	for(i=0; i<num; i++)
	{
		attrib_map[i]=i;
	}

	if(print_table(num, attrib_map, NULL))
	{
		FATAL("Unable to print table %s\n", table_info.table_name);
		return INTERNAL_ERROR;
	}

	return (0);
}

void table :: commit()
{
	int n=0;
	bplustree *b1;
	indexinfo *iinfo;

	DBGENTER;
	/*** Update all the information before commiting ***/
	table_info.num_indexes = ilist.size();
	for(list<bplustree *>::iterator i= ilist.begin(); i != ilist.end(); i++)
	{
		b1 = *i;
		iinfo = &table_info.index_info[n++];

		/*** Get the latest data before writing to catalog ***/
		strcpy(iinfo->iname, b1->iname);
		iinfo->offset	= b1->attrib_id;
		iinfo->dups	= b1->dups;
		iinfo->root_block	= b1->mRootBlockNo;
		iinfo->block_count	= b1->num_blocks;
		b1->commit();
	}

	stm1->flush();
}

table :: ~table()
{
	commit();
	delete stm1;
}

#endif
